package top.cardone.context.util;

import com.google.common.collect.Maps;
import com.google.common.collect.Table;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.core.io.Resource;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import top.cardone.cache.Cache;
import top.cardone.context.ApplicationContextHolder;
import top.cardone.core.util.func.Func0;
import top.cardone.core.util.func.Func1;
import top.cardone.core.util.func.Func2;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by cardone-home-001 on 2016/4/20.
 */
@Log4j2
public class MapUtils extends org.apache.commons.collections.MapUtils {
	/**
	 * 转换集合
	 *
	 * @param map         原集合
	 * @param configTable 配置
	 * @param <V>         类型
	 * @return 新集合
	 */
	public static <V> Map<String, V> newHashMap(Map<String, V> map, Table<String, String, String> configTable) {
		if (isEmpty(map) || (configTable == null) || configTable.isEmpty()) {
			return map;
		}

		HashMap<String, V> newMap = Maps.newHashMap();

		for (String rowKey : configTable.rowKeySet()) {
			if (!map.containsKey(rowKey)) {
				continue;
			}

			String newKey = StringUtils.defaultIfBlank(configTable.get(rowKey, "newName"), rowKey);

			if (StringUtils.isBlank(newKey)) {
				continue;
			}

			String funcBeanIds = StringUtils.defaultString(configTable.get(rowKey, "funcBeanIds"), configTable.get(rowKey, "funcBeanId"));

			if (StringUtils.isBlank(funcBeanIds)) {
				newMap.put(newKey, map.get(rowKey));

				continue;
			}

			Object newValue = map.get(rowKey);

			String[] funcBeanIdArray = StringUtils.split(funcBeanIds);

			for (String funcBeanId : funcBeanIdArray) {
				newValue = ApplicationContextHolder.getBean(Func1.class, funcBeanId).func(newValue);
			}

			newMap.put(newKey, (V) newValue);
		}

		return newMap;
	}

	/**
	 * 转换集合
	 *
	 * @param <V>       类型
	 * @param map       原集合
	 * @param mapperMap 配置
	 * @return 新集合
	 */
	public static <V> Map<String, V> newHashMap(Map<String, V> map, Map<String, Object> mapperMap) {
		if (isEmpty(mapperMap)) {
			return map;
		}

		if (map == null) {
			map = Maps.newHashMap();
		}

		HashMap<String, V> newMap = Maps.newHashMap();

		for (Map.Entry<String, Object> mapperEntry : mapperMap.entrySet()) {
			if (mapperEntry.getValue() == null) {
				newMap.put(mapperEntry.getKey(), null);

				continue;
			}

			if (mapperEntry.getValue() instanceof String) {
				if (map.containsKey(mapperEntry.getValue())) {
					newMap.put(mapperEntry.getKey(), map.get(mapperEntry.getValue()));
				}

				continue;
			}

			if (mapperEntry.getValue() instanceof Map) {
				Map<String, Object> itemMapper = (Map<String, Object>) mapperEntry.getValue();

				for (Map.Entry<String, Object> itemMapperEntry : itemMapper.entrySet()) {
					Object func = ApplicationContextHolder.getBean(itemMapperEntry.getKey());

					if (func == null) {
						continue;
					}

					if (func instanceof Func2) {
						newMap.put(mapperEntry.getKey(), (V) ((Func2) func).func(map, itemMapperEntry.getValue()));
					} else if (func instanceof Func1) {
						newMap.put(mapperEntry.getKey(), (V) ((Func1) func).func(map.get(itemMapperEntry.getValue())));
					} else if (func instanceof Func0) {
						newMap.put(mapperEntry.getKey(), (V) ((Func0) func).func());
					}
				}

				continue;
			}

			newMap.put(mapperEntry.getKey(), (V) mapperEntry.getValue());
		}

		return newMap;
	}

	public static <V> Map<String, V> newHashMap(Map<String, V> map, String resourcePath, boolean cache) {
		return newHashMap(map, newMap(resourcePath, cache));
	}

	public static Map toMap(Object obj, String objectKey) {
		return toMap(obj, "objs", objectKey);
	}

	public static Map toMap(Object obj, String objectsKey, String objectKey) {
		Map<Object, Object> newMap;

		if (obj == null) {
			newMap = Maps.newHashMap();
		} else if (obj instanceof Map) {
			newMap = (Map) obj;
		} else if ((obj instanceof Iterable) || ObjectUtils.isArray(obj)) {
			newMap = Maps.newHashMap();

			newMap.put(objectsKey, obj);
		} else {
			newMap = Maps.newHashMap();

			if (obj.getClass().isPrimitive() ||
					obj.getClass().equals(String.class) ||
					obj.getClass().equals(Integer.class) ||
					obj.getClass().equals(Byte.class) ||
					obj.getClass().equals(Long.class) ||
					obj.getClass().equals(Double.class) ||
					obj.getClass().equals(Float.class) ||
					obj.getClass().equals(Character.class) ||
					obj.getClass().equals(Short.class) ||
					obj.getClass().equals(BigDecimal.class) ||
					obj.getClass().equals(BigInteger.class) ||
					obj.getClass().equals(Boolean.class) ||
					obj.getClass().equals(Date.class)) {
				newMap.put(org.apache.commons.lang3.StringUtils.defaultString(objectKey, "obj"), obj);

				return newMap;
			}

			ReflectionUtils.doWithFields(obj.getClass(), field -> {
				Object value = FieldUtils.readField(field, obj, true);

				newMap.put(field.getName(), value);
			}, ReflectionUtils.COPYABLE_FIELDS);
		}

		return newMap;
	}


	public static <K, V> Map<K, V> newMap(Resource resource) {
		if (!resource.exists()) {
			return Maps.newHashMap();
		}

		try {
			String json = FileUtils.readFileToString(resource.getFile());

			return ApplicationContextHolder.getBean(Gson.class).fromJson(json, new TypeToken<Map<K, V>>() {
			}.getType());
		} catch (IOException e) {
			log.error(e);

			return Maps.newHashMap();
		}
	}

	public static <K, V> Map<K, V> newMap(String resourcePath, boolean cache) {
		if (cache) {
			return ApplicationContextHolder.getBean(Cache.class).get("table", resourcePath, () -> {
				Resource resource = ApplicationContextHolder.getApplicationContext().getResource(resourcePath);

				return newMap(resource);
			});
		}

		Resource resource = ApplicationContextHolder.getApplicationContext().getResource(resourcePath);

		return newMap(resource);
	}

	public static <K, V> Map<K, V> newMap(String resourcePath) {
		return newMap(resourcePath, true);
	}
}